﻿using System;
using System.IO;
using System.Xml;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using System.Collections.Generic;

namespace SPSource.Lists
{
    public class ListTemplateSchemaGenerator
    {
        /// <summary>
        /// Creates schema.xml file for ListTemplate based on schema.xml 
        /// http://msdn.microsoft.com/en-us/library/ms459356.aspx
        /// http://msdn.microsoft.com/en-us/library/aa543477.aspx
        /// </summary>
        /// <param name="directory"></param>
        /// <param name="listName"></param>
        /// <param name="webUrl"></param>
        public static FileInfo GenerateListSchemaXml(DirectoryInfo directory, string listName, string webUrl, int listTemplateType)
        {
            using (SPSite site = new SPSite(webUrl))
            {
                using (SPWeb web = site.OpenWeb())
                {
                    SPList list = web.Lists[listName];
                    
                    string listStaticName = SPStringUtility.RemoveNonAlphaNumericChars(listName);
                    DirectoryInfo listSchemaDirectory = directory.CreateSubdirectory(listStaticName);

                    XmlDocument listSchemaDoc = new XmlDocument();
                    XmlNamespaceManager nsmgr = new XmlNamespaceManager(listSchemaDoc.NameTable);
                    nsmgr.AddNamespace(string.Empty, Constants.SharePointXmlNamespace);

                    XmlNode xmlListNode = BuildListNode(listSchemaDoc, Constants.SharePointXmlNamespace, list, listStaticName, listTemplateType);
                    listSchemaDoc.AppendChild(xmlListNode);

                    XmlNode metaDataNode = listSchemaDoc.CreateElement("MetaData");
                    //NOTE: CAML MetaData Element (List) - http://msdn.microsoft.com/en-us/library/ms461161.aspx
                    metaDataNode = BuildViewsNode(listSchemaDoc, metaDataNode, list);
                    metaDataNode = BuildFieldsNode(listSchemaDoc, metaDataNode, list, site);
                    metaDataNode = BuildContentTypesNode(listSchemaDoc, metaDataNode, web, list);
                    metaDataNode = BuildFormsNode(listSchemaDoc, metaDataNode, list);
                    metaDataNode = BuildSecurityNode(listSchemaDoc, metaDataNode);
                    metaDataNode = BuildDocumentLibraryTemplateNode(listSchemaDoc, metaDataNode, list);
                    metaDataNode = BuildToolbarNode(listSchemaDoc, metaDataNode, list);
                    //TODO: DefaultDescription - http://msdn.microsoft.com/en-us/library/ms427533.aspx
                    //TODO: DefaultElement - http://msdn.microsoft.com/en-us/library/aa977188.aspx
                    xmlListNode.AppendChild(metaDataNode);

                    string schemaFilePath = listSchemaDirectory.FullName + "\\schema.xml";
                    listSchemaDoc.Save(listSchemaDirectory.FullName + "\\schema.xml");
                    return new FileInfo(schemaFilePath);
                }
            }
        }

        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/ms415091.aspx
        /// </summary>
        /// <param name="listSchemaDoc"></param>
        /// <param name="schema"></param>
        /// <param name="list"></param>
        /// <returns></returns>
        private static XmlNode BuildListNode(XmlDocument listSchemaDoc, string schema, SPList list, string listStaticName, int listTemplateType)
        {
            XmlNode xmlListNode = listSchemaDoc.CreateElement("List");
            XmlAttribute xmlnsAttr = listSchemaDoc.CreateAttribute("xmlns");
            xmlnsAttr.Value = schema;
            xmlListNode.Attributes.Append(xmlnsAttr);

            //BaseType = "Integer" (optional)
            XmlAttribute baseTypeAttr = listSchemaDoc.CreateAttribute("BaseType");
            baseTypeAttr.Value = ListEnumHelpers.ReturnBaseType(list);
            xmlListNode.Attributes.Append(baseTypeAttr);

            //Default = "TRUE" | "FALSE" (optional)
            //XmlAttribute defaultAttr = listSchemaDoc.CreateAttribute("Default");
            //defaultAttr.Value = "0";//TODO: not sure where this comes from!
            //xmlListNode.Attributes.Append(defaultAttr);
            
            //DefaultItemOpen = "Integer" (optional)
            XmlAttribute defaultItemOpenAttr = listSchemaDoc.CreateAttribute("DefaultItemOpen");
            defaultItemOpenAttr.Value = ListEnumHelpers.ReturnDefaultItemOpen(list);
            xmlListNode.Attributes.Append(defaultItemOpenAttr);

            //Description  = "Text"(optional)
            if (String.IsNullOrEmpty(list.Description))
            {
                XmlAttribute descriptionAttr = listSchemaDoc.CreateAttribute("Description");
                descriptionAttr.Value = list.Description;
                xmlListNode.Attributes.Append(descriptionAttr);
            }

            //Direction="TEXT" (required)
            XmlAttribute directionAttr = listSchemaDoc.CreateAttribute("Direction");
            directionAttr.Value = list.Direction;
            xmlListNode.Attributes.Append(directionAttr);

            //DisableAttachments = "TRUE" | "FALSE"
            XmlAttribute disableAttachmentsAttr = listSchemaDoc.CreateAttribute("DisableAttachments");
            disableAttachmentsAttr.Value = (!list.EnableAttachments).ToString();
            xmlListNode.Attributes.Append(disableAttachmentsAttr);
            
            //DraftVersionVisibility = "Integer" (optional)
            XmlAttribute draftVersionVisibilityAttr = listSchemaDoc.CreateAttribute("DraftVersionVisibility");
            draftVersionVisibilityAttr.Value = ListEnumHelpers.ReturnDraftVersionVisibility(list);
            xmlListNode.Attributes.Append(draftVersionVisibilityAttr);

            //EnableContentTypes = "TRUE" | "FALSE" (optional)
            XmlAttribute enableContentTypesAttr = listSchemaDoc.CreateAttribute("EnableContentTypes");
            enableContentTypesAttr.Value = list.ContentTypesEnabled.ToString();
            xmlListNode.Attributes.Append(enableContentTypesAttr);

            //EnableThumbnails = "TRUE" | "FALSE" (optional)
            //XmlAttribute enableThumbnailsAttr = listSchemaDoc.CreateAttribute("EnableThumbnails");
            //enableThumbnailsAttr.Value = //TODO: not sure where this comes from!
            //xmlListNode.Attributes.Append(enableThumbnailsAttr);

            //EventSinkAssembly = "Text" (optional)
            if (!String.IsNullOrEmpty(list.EventSinkAssembly))
            {
                XmlAttribute eventSinkAssemblyAttr = listSchemaDoc.CreateAttribute("EventSinkAssembly");
                eventSinkAssemblyAttr.Value = list.EventSinkAssembly;
                xmlListNode.Attributes.Append(eventSinkAssemblyAttr);
                XmlAttribute eventSinkClassAttr = listSchemaDoc.CreateAttribute("EventSinkClass");
                eventSinkClassAttr.Value = list.EventSinkClass;
                xmlListNode.Attributes.Append(eventSinkClassAttr);
            }

            //FolderCreation = "TRUE" | "FALSE" (optional)
            XmlAttribute folderCreationAttr = listSchemaDoc.CreateAttribute("FolderCreation");
            folderCreationAttr.Value = list.EnableFolderCreation.ToString();
            xmlListNode.Attributes.Append(folderCreationAttr);

            //ModeratedList = "TRUE" | "FALSE" (optional) - this is actually content approval switch
            XmlAttribute moderatedListAttr = listSchemaDoc.CreateAttribute("ModeratedList");
            moderatedListAttr.Value = list.EnableModeration.ToString();
            xmlListNode.Attributes.Append(moderatedListAttr);
            
            //NOTE: ModerationType = "TRUE" | "FALSE" (optional) - this is deprecated
            
            //Name= "TEXT" (required)
            XmlAttribute nameAttr = listSchemaDoc.CreateAttribute("Name");
            nameAttr.Value = listStaticName;
            xmlListNode.Attributes.Append(nameAttr);

            //OrderedList = "TRUE" | "FALSE" (optional)
            XmlAttribute orderedListAttr = listSchemaDoc.CreateAttribute("OrderedList");
            orderedListAttr.Value = list.Ordered.ToString();
            xmlListNode.Attributes.Append(orderedListAttr);

            //PrivateList = "TRUE" | "FALSE" (optional)
            //XmlAttribute privateListAttr = listSchemaDoc.CreateAttribute("PrivateList");
            //privateListAttr.Value = //TODO: not sure where this comes from!
            //xmlListNode.Attributes.Append(privateListAttr);
            
            //QuickLaunchUrl = "URL" (optional)
            XmlAttribute quickLaunchUrlAttr = listSchemaDoc.CreateAttribute("QuickLaunchUrl");
            quickLaunchUrlAttr.Value = list.OnQuickLaunch.ToString();
            xmlListNode.Attributes.Append(quickLaunchUrlAttr);
            
            //RootWebOnly = "TRUE" | "FALSE" (optional)
            //XmlAttribute rootWebOnlyAttr = listSchemaDoc.CreateAttribute("RootWebOnly");
            //rootWebOnlyAttr.Value = //TODO: not sure where this comes from!
            //xmlListNode.Attributes.Append(rootWebOnlyAttr);
            
            //Title="TEXT" (required)
            XmlAttribute titleAttr = listSchemaDoc.CreateAttribute("Title");
            titleAttr.Value = list.Title;
            xmlListNode.Attributes.Append(titleAttr);

            //Type="INTEGER" (optional)
            XmlAttribute typeAttr = listSchemaDoc.CreateAttribute("Type");
            typeAttr.Value = listTemplateType.ToString();
            xmlListNode.Attributes.Append(typeAttr);

            //URLEncode = "TRUE" | "FALSE" (optional)
            //XmlAttribute uRLEncodeAttr = listSchemaDoc.CreateAttribute("URLEncode");
            //uRLEncodeAttr.Value = //TODO: not sure where this comes from!
            //xmlListNode.Attributes.Append(uRLEncodeAttr);

            //VersioningEnabled = "TRUE" | "FALSE" (optional)
            XmlAttribute versioningEnabledAttr = listSchemaDoc.CreateAttribute("VersioningEnabled");
            versioningEnabledAttr.Value = list.EnableVersioning.ToString();
            xmlListNode.Attributes.Append(versioningEnabledAttr);


            if (list is SPPictureLibrary)
            {
                //ThumbnailSize = "Integer" (optional)
                XmlAttribute thumbnailSizeAttr = listSchemaDoc.CreateAttribute("ThumbnailSize");
                thumbnailSizeAttr.Value = ((SPPictureLibrary)list).ThumbnailSize.ToString();
                xmlListNode.Attributes.Append(thumbnailSizeAttr);

                //WebImageHeight = "Integer" (optional)
                XmlAttribute webImageHeightAttr = listSchemaDoc.CreateAttribute("WebImageHeight");
                webImageHeightAttr.Value = ((SPPictureLibrary)list).WebImageHeight.ToString();
                xmlListNode.Attributes.Append(webImageHeightAttr);

                //WebImageWidth = "Integer" (optional)
                XmlAttribute webImageWidthAttr = listSchemaDoc.CreateAttribute("WebImageWidth");
                webImageWidthAttr.Value = ((SPPictureLibrary)list).WebImageWidth.ToString();
                xmlListNode.Attributes.Append(webImageWidthAttr);
            }
            //Url= "URL"
            XmlAttribute urlAttr = listSchemaDoc.CreateAttribute("Url");
            urlAttr.Value = "Lists/" + list.RootFolder.Name;
            xmlListNode.Attributes.Append(urlAttr);

            //Id="Guid" 
            XmlAttribute idAttr = listSchemaDoc.CreateAttribute("Id");
            idAttr.Value = Guid.NewGuid().ToString();
            xmlListNode.Attributes.Append(idAttr);

            return xmlListNode;
        }

        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/ms450295.aspx
        /// </summary>
        /// <param name="listSchemaDoc"></param>
        /// <param name="metaDataNode"></param>
        /// <param name="list"></param>
        /// <returns></returns>
        private static XmlNode BuildToolbarNode(XmlDocument listSchemaDoc, XmlNode metaDataNode, SPList list)
        {
            XmlNode toolbarNode = listSchemaDoc.CreateElement("Toolbar");
            XmlAttribute typeAttr = listSchemaDoc.CreateAttribute("Type");
            typeAttr.Value = "RelatedTasks";//TODO: not sure where to get this from?!?
            toolbarNode.Attributes.Append(typeAttr);
            metaDataNode.AppendChild(toolbarNode);
            return metaDataNode;
        }

        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/ms464220.aspx
        /// </summary>
        /// <param name="listSchemaDoc"></param>
        /// <param name="metaDataNode"></param>
        /// <param name="list"></param>
        /// <returns></returns>
        private static XmlNode BuildFormsNode(XmlDocument listSchemaDoc, XmlNode metaDataNode, SPList list)
        {
            XmlNode formsNode = listSchemaDoc.CreateElement("Forms");
            foreach (SPForm form in list.Forms)
            {
                formsNode.InnerXml += form.SchemaXml;
            }
            metaDataNode.AppendChild(formsNode);
            return metaDataNode;
        }

        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/ms451470.aspx
        /// </summary>
        /// <param name="listSchemaDoc"></param>
        /// <param name="metaDataNode"></param>
        /// <param name="list"></param>
        /// <returns></returns>
        private static XmlNode BuildFieldsNode(XmlDocument listSchemaDoc, XmlNode metaDataNode, SPList list, SPSite site)
        {
            XmlNode fieldsNode = listSchemaDoc.CreateElement("Fields");
            foreach (SPField field in list.Fields)
            {
                bool processed = false;
                if (field is SPFieldLookup && !(field is SPFieldUser))
                {
                    //TODO: BUG: #23492 - apparently if custom field inheriting SPFieldLookUp it falls over :-(
                    //NOTE: for lookups replace List attribute and remove WebId attribute
                    XmlDocument fieldDocument = new XmlDocument();
                    fieldDocument.InnerXml = field.SchemaXml;
                    if (fieldDocument.InnerXml.Contains("WebId"))
                    {
                        string webId = fieldDocument.ChildNodes[0].Attributes["WebId"].Value;
                        string listId = fieldDocument.ChildNodes[0].Attributes["List"].Value;
                        string lookupListUrl;
                        using (SPWeb lookupWeb = site.AllWebs[new Guid(webId)])
                        {
                            lookupListUrl = lookupWeb.Lists[new Guid(listId)].RootFolder.Url;
                        }
                        fieldDocument.ChildNodes[0].Attributes.Remove(fieldDocument.ChildNodes[0].Attributes["WebId"]);
                        fieldDocument.ChildNodes[0].Attributes["List"].Value = lookupListUrl;
                        fieldsNode.InnerXml += fieldDocument.InnerXml;
                        processed = true;
                    }
                }
                if (!processed)
                {
                    fieldsNode.InnerXml += field.SchemaXml;
                }
            }
            metaDataNode.AppendChild(fieldsNode);
            return metaDataNode;
        }

        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/ms457339.aspx
        /// </summary>
        /// <param name="listSchemaDoc"></param>
        /// <param name="metaDataNode"></param>
        /// <param name="list"></param>
        /// <returns></returns>
        private static XmlNode BuildViewsNode(XmlDocument listSchemaDoc, XmlNode metaDataNode, SPList list)
        {
            XmlNode viewsNode = listSchemaDoc.CreateElement("Views");
            List<int> baseViewIdCollection = new List<int>();
            foreach (SPView view in list.Views)
            {
                XmlDocument viewXml = new XmlDocument();
                viewXml.LoadXml(view.HtmlSchemaXml);
                XmlNode viewElement = viewXml.SelectSingleNode("/*");

                //NOTE: needed but not in HtmlSchemaXml!
                XmlAttribute webPartZoneIDAttr = viewXml.CreateAttribute("WebPartZoneID");
                webPartZoneIDAttr.Value = "Main";
                viewElement.Attributes.Append(webPartZoneIDAttr);

                //NOTE: HtmlSchemaXml stores incorrect Url
                string urlAttr = viewElement.Attributes["Url"].Value;
                int lastIndexOfSlash = urlAttr.LastIndexOf("/") + 1;
                viewElement.Attributes["Url"].Value = urlAttr.Substring(lastIndexOfSlash, urlAttr.Length - lastIndexOfSlash);

                //NOTE: BaseViewID is wrong - must be a unique identifier for schema!
                if (viewElement.Attributes["BaseViewID"] != null)
                {
                    int baseViewIDAttr = int.Parse(viewElement.Attributes["BaseViewID"].Value.ToString());
                    //does it already exist in views
                    if(baseViewIdCollection.Contains(baseViewIDAttr))
                    {
                        int newBaseViewID = 1;
                        while (baseViewIdCollection.Contains(newBaseViewID))
                        {
                            newBaseViewID++;
                        }
                        viewElement.Attributes["BaseViewID"].Value = newBaseViewID.ToString();
                    }
                    else
                    {
                        baseViewIdCollection.Add(baseViewIDAttr);
                        //NOTE: no need to change
                    }
                }
                
                viewsNode.AppendChild(listSchemaDoc.ImportNode(viewElement, true));

            }
            metaDataNode.AppendChild(viewsNode);
            return metaDataNode;
        }

        /// <summary>
        /// http://msdn.microsoft.com/en-us/library/aa544130.aspx
        /// </summary>
        /// <param name="listSchemaDoc"></param>
        /// <param name="metaDataNode"></param>
        /// <param name="list"></param>
        /// <returns></returns>
        private static XmlNode BuildContentTypesNode(XmlDocument listSchemaDoc, XmlNode metaDataNode, SPWeb web, SPList list)
        {
            XmlNode contentTypesNode = listSchemaDoc.CreateElement("ContentTypes");
            SPContentTypeCollection cts = list.ContentTypes;

            foreach (SPContentType ct in cts)
            {
                contentTypesNode.InnerXml += ct.SchemaXml;
            }
            metaDataNode.AppendChild(contentTypesNode);
            return metaDataNode;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="listSchemaDoc"></param>
        /// <param name="metaDataNode"></param>
        /// <param name="list"></param>
        /// <returns></returns>
        private static XmlNode BuildDocumentLibraryTemplateNode(XmlDocument listSchemaDoc, XmlNode metaDataNode, SPList list)
        {
            if (list is SPDocumentLibrary && !(list is SPPictureLibrary))
            {
                XmlNode documentLibraryTemplateNode = listSchemaDoc.CreateElement("DocumentLibraryTemplate");
                documentLibraryTemplateNode.InnerText = GetDocumentTemplateUrl(list);
                metaDataNode.AppendChild(documentLibraryTemplateNode);
            }
            return metaDataNode;
        }

        /// <summary>
        /// From private string DocumentTemplateFromDocLib() using Reflector
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        private static string GetDocumentTemplateUrl(SPList list)
        {
            SPDocumentLibrary docList = list as SPDocumentLibrary;
            if (docList.ContentTypes[0].Id.IsChildOf(SPBuiltInContentTypeId.Document) && (docList != null))
            {
                if (string.IsNullOrEmpty(docList.DocumentTemplateUrl))
                {
                    return string.Empty;
                }
                string serverRelativeUrl = docList.ParentWeb.ServerRelativeUrl;
                if (!serverRelativeUrl.EndsWith("/"))
                {
                    return (serverRelativeUrl + "/" + docList.DocumentTemplateUrl);
                }
                return (serverRelativeUrl + docList.DocumentTemplateUrl);
            }
            return string.Empty;
        }

        private static XmlNode BuildSecurityNode(XmlDocument listSchemaDoc, XmlNode metaDataNode)
        {
            XmlNode securityNode = listSchemaDoc.CreateElement("Security");

            XmlElement readSecurityNode = listSchemaDoc.CreateElement("ReadSecurity");
            readSecurityNode.InnerXml = "1";//TODO: need to get these!
            securityNode.AppendChild(readSecurityNode);

            XmlElement writeSecurityNode = listSchemaDoc.CreateElement("WriteSecurity");
            writeSecurityNode.InnerXml = "1";//TODO: need to get these!
            securityNode.AppendChild(writeSecurityNode);

            XmlElement schemaSecurityNode = listSchemaDoc.CreateElement("SchemaSecurity");
            schemaSecurityNode.InnerXml = "0";//TODO: need to get these!
            securityNode.AppendChild(schemaSecurityNode);

            metaDataNode.AppendChild(securityNode);
            return metaDataNode;
        }
    }
}
